<?php

class DocFormatter 
{

	var $in_pre;
	var $prenum;
	var $headings;
	var $list_stack;
	var $link_suffix;
	var $resulting_text;
	var $newlines;
	var $ignore_next_newline;

	function DocFormatter ()
	{
		$this->link_suffix = ".html";
		
	}

	function get_file_title ($file)
	{
		$handle = fopen ($file, "rb");
		if ($handle == false) return false;	

		$title = "";
		while (!feof($handle)) {
			$line = fgets ($handle);
			$line = trim($line);

			if ($line == "") continue;

			$words = explode (" ", $line);
			if ($words[0] != ".title") break;

			$title = substr ($line, strlen($words[0]));
			break;
		}

		fclose ($handle);

		if ($title == "") return $title;
		return "<div id=wikiTitle>" . $title . "</div>";
	}

	function format_file ($file)
	{
		$handle = fopen ($file, "rb");
		if ($handle == false) return false;	

		$this->in_pre = false;
		$this->prenum = -1;
		$this->headings = array ();
		$this->list_stack = array ();
		$this->newlines = "";
		$this->ignore_next_newline = false;

		// skip initial blank lines and directives lines
		while (!feof($handle)) {
			$line = fgets ($handle);
			$line = trim($line);

			if ($line == "") continue;

			$words = explode (" ", $line);
			if ($words[0] != ".title") break;
		}

		$this->resulting_text = "<div id=wikiBody>";
		while (!feof($handle)) {
			//$line = fgets ($handle);

			$len = strlen($line);
			if ($len >= 2 && substr($line,$len-2,2) == "\r\n") {
				$line = substr($line, 0, $len-2);
			}
			else if ($len >= 1 && ($line{$len-1} == "\n" || $line{$len-1} == "\r")) {
				$line = substr($line, 0, $len-1);
			}

			$this->resulting_text = $this->resulting_text . $this->format_line ($line);

			# fgets may not read an entire line 
			# if you php is earlier than 4.3
			$line = fgets ($handle);
		}
		$this->resulting_text = $this->resulting_text . "</div>";

		fclose ($handle);

		$heading = $this->emit_headings ();

		if ($heading == "") return $this->resulting_text;
		//else return "<b><i>Contents</i></b>\n" . $heading . $this->resulting_text;
		else return $heading . $this->resulting_text;
	}

	function format_page ($page)
	{
		$page_lines = explode ("\n", $page);

		$this->in_pre = false;
		$this->headings = array ();
		$this->list_stack = array ();

		$this->resulting_text = "<div id=wikiBody>";
		foreach ($page_lines as $line) {
			$this->resulting_text = $this->resulting_text . $this->format_line ($line);
		}
		$this->resulting_text = $this->resulting_text . "</div>";

		$heading = $this->emit_headings ();

		if ($heading == "") return $this->resulting_text;
		//else return "<b><i>Contents</i></b>\n" . $heading . $this->resulting_text;
		else return $heading . $this->resulting_text;
	}

	function get_heading_num ()
	{
		$count = count($this->headings);
		if ($count <= 0) return "";

		$stack = array ();
		$index = 1;
		$num = "";
		for ($i = 0; $i < $count; $i++) 
		{
			$depth = $this->headings[$i][0];

			if ($i == 0 || $depth == $prev_depth) {
				/* don't do anything */
			}
			else if ($depth < $prev_depth) {
				for ($j = $depth; $j < $prev_depth; $j++)  {
					if (count($stack) > 0) {
							$index = array_pop($stack) + 1;
					}
				}
			}
			else {
				array_push ($stack, $index - 1);
				$index = 1;
			}

			if ($i == $count - 1) {
				for ($x = 0; $x < count($stack); $x++) {
					$num = $num . sprintf ("%d", $stack[$x]) . ".";
				}
				$num = $num . sprintf ("%d", $index);
			}

			$index = $index + 1;
			$prev_depth = $depth;
		}

		return $num;
	}

	function emit_headings ()
	{
		$text = "";

		$count = count($this->headings);
		if ($count <= 0) return $text;
		
		$text = $text . "<div id=wikiContent><dl><dt>\n";
		$text = $text . "<dl>\n";

		$stack = array ();
		$index = 1;
		for ($i = 0; $i < $count; $i++) {
			$depth = $this->headings[$i][0];
			$title = $this->headings[$i][1];

			if ($i == 0 || $depth == $prev_depth) {
				/* don't do anything */
			}
			else if ($depth < $prev_depth) {
				for ($j = $depth; $j < $prev_depth; $j++)  {
					if (count($stack) > 0) {
						$index = array_pop($stack) + 1;
					}
					$text = $text . "</dl>\n";
				}
			}
			else {
				array_push ($stack, $index - 1);
				$index = 1;

				$text = $text . "<dl>\n";
			}

			$num = "";
			for ($x = 0; $x < count($stack); $x++) {
				$num = $num . sprintf ("%d", $stack[$x]) . ".";
			}
			$num = $num . sprintf ("%d", $index);
			$text = $text . sprintf ("<dt>%s <a href='#toc-%d'>%s</a></dt>\n", $num, $i, $title);

			$index = $index + 1;
			$prev_depth = $depth;
			$prev_title = $title;
		}

		$text = $text . "</dl>\n";
		$text = $text . "</dt></dl></div>\n";

		return $text;
	}

	function clear_list ()
	{
		$text = "";
		while (($cnt = count($this->list_stack)) > 0) {
			if ($this->list_stack[$cnt-1][1] == "ol")
				$text = $text . "</ol>\n";
			else if ($this->list_stack[$cnt-1][1] == "ul")
				$text = $text . "</ul>\n";
			array_pop ($this->list_stack);
		}
		return $text;
	}

	function format_line ($line)
	{
		$tmp = $this->format_prenum ($line);
		if ($tmp != false) {
			$this->newlines = "";
			$this->ignore_next_newline = true;
			return $this->clear_list() . $tmp . "\n";
		}

		$tmp = $this->format_pre ($line);
		if ($tmp != false) {
			$this->newlines = "";
			$this->ignore_next_newline = true;
			return $this->clear_list() . $tmp . "\n";
		}

		if ($this->in_pre) {
			$this->newlines = "";
			$this->ignore_next_newline = true;

			# the text in a <pre></pre> block
			//return htmlspecialchars($line) . "\n";
			return $this->pretext($line) . "\n";
		}

		if (trim($line) == "") {
			if ($this->ignore_next_newline == false) {
				// just compact the multiple consecutive newlines.
				// $this->newlines = $this->newlines . "<br>\n";
				$this->newlines = "<br>\n";
			}

			return $this->clear_list();
		}

		$tmp = $this->format_heading ($line);
		if ($tmp != false) {
			$this->newlines = "";
			$this->ignore_next_newline = true;
			return $this->clear_list() . $tmp . "\n";
		}

		$tmp = $this->format_list ($line);
		if ($tmp != false) {
			$this->newlines = "";
			$this->ignore_next_newline = true;
			// TODO: furthur attribute processing... bold, italic
			return $tmp . "\n";
		}

		$tmp = $this->clear_list() . $this->newlines . $this->format_text($line) . "<br>\n";
		$this->newlines = "";
		$this->ignore_next_newline = false;
		return $tmp;
	}

	function pretext ($text)
	{
		if ($this->prenum >= 0)
		{
			$this->prenum++;
			return "<font class=linenum>" . 
			sprintf ("%05d", $this->prenum) . 
			       "</font> " . htmlspecialchars($text);
		}
		else
		{
			return htmlspecialchars($text);
		}
	}

	function format_prenum ($line)
	{
		# {{{{ something }}}}  ===> the line number is not supported
		$pattern = '/^[[:space:]]*{{{{([^{].*[^}]|[^{}])}}}}[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			if ($this->in_pre == false) {
				$text = preg_replace ('/^[[:space:]]*{{{{/', '', $line, 1);
				$text = preg_replace ('/}}}}[[:space:]]*$/', '', $text, 1);
				return '<pre class=wiki>' . htmlspecialchars($text) . '</pre>';
			}
			else return htmlspecialchars($line);
		} 

		if ($this->in_pre == false) {
			# {{{{ something
			$pattern = '/^[[:space:]]*{{{{[^{]/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = true;
				$this->prenum = 0;
				$text = preg_replace ('/^[[:space:]]*{{{{/', '', $line, 1);
				//return '<pre class=wiki>' . htmlspecialchars($text);
				return '<pre class=wiki>' . $this->pretext($text);
			}

			# {{{{
			$pattern = '/^[[:space:]]*{{{{[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = true;
				$this->prenum = 0;
				$text = preg_replace ('/^[[:space:]]*{{{{/', '', $line, 1);
				return '<pre class=wiki>' . $text;
			}
		}
		else {
			# something }}}}
			$pattern = '/[^}]}}}}[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = false;
				$this->prenum = -1;
				$text = preg_replace ('/}}}}[[:space:]]*$/', '', $line, 1);
				//return htmlspecialchars($text) . '</pre>';
				return $this->pretext($text) . '</pre>';
			}

			# }}}}
			$pattern = '/^[[:space:]]*}}}}[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = false;
				$this->prenum = -1;
				$text = preg_replace ('/}}}}[[:space:]]*$/', '', $line, 1);
				return $text . '</pre>';
			}
		}

		return false;
	}

	function format_pre ($line)
	{
		# {{{ something }}} 
		$pattern = '/^[[:space:]]*{{{([^{].*[^}]|[^{}])}}}[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			if ($this->in_pre == false) {
				$text = preg_replace ('/^[[:space:]]*{{{/', '', $line, 1);
				$text = preg_replace ('/}}}[[:space:]]*$/', '', $text, 1);
				return '<pre class=wiki>' . htmlspecialchars($text) . '</pre>';
			}
			else return htmlspecialchars($line);
		} 

		if ($this->in_pre == false) {
			# {{{ something
			$pattern = '/^[[:space:]]*{{{[^{]/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = true;
				$text = preg_replace ('/^[[:space:]]*{{{/', '', $line, 1);
				return '<pre class=wiki>' . htmlspecialchars($text);
			}

			# {{{
			$pattern = '/^[[:space:]]*{{{[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = true;
				$text = preg_replace ('/^[[:space:]]*{{{/', '', $line, 1);
				return '<pre class=wiki>' . $text;
			}
		}
		else {
			# something }}}	
			$pattern = '/[^}]}}}[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = false;
				$text = preg_replace ('/}}}[[:space:]]*$/', '', $line, 1);
				return htmlspecialchars($text) . '</pre>';
			}

			# }}}
			$pattern = '/^[[:space:]]*}}}[[:space:]]*$/';
			if (preg_match($pattern, $line) > 0) {
				$this->in_pre = false;
				$text = preg_replace ('/}}}[[:space:]]*$/', '', $line, 1);
				return $text . '</pre>';
			}
		}

		return false;
	}

	function format_heading ($line)
	{
		# ===== heading =====
		$pattern = '/^[[:space:]]*=====([^=].*[^=]|[^=])=====[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*=====/', '', $line, 1);
			$text = preg_replace ('/=====[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(5, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h5 class=wiki><a name='#toc-%d'>%s %s</a></h5>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# ==== heading ====
		$pattern = '/^[[:space:]]*====([^=].*[^=]|[^=])====[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*====/', '', $line, 1);
			$text = preg_replace ('/====[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(4, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h4 class=wiki><a name='#toc-%d'>%s %s</a></h4>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# === heading ===
		$pattern = '/^[[:space:]]*===([^=].*[^=]|[^=])===[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*===/', '', $line, 1);
			$text = preg_replace ('/===[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(3, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h3 class=wiki><a name='#toc-%d'>%s %s</a></h3>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# == heading ==
		$pattern = '/^[[:space:]]*==([^=].*[^=]|[^=])==[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*==/', '', $line, 1);
			$text = preg_replace ('/==[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(2, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h2 class=wiki><a name='#toc-%d'>%s %s</a></h2>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		# = heading =
		$pattern = '/^[[:space:]]*=([^=].*[^=]|[^=])=[[:space:]]*$/';
		if (preg_match($pattern, $line) > 0) {
			$text = preg_replace ('/^[[:space:]]*=/', '', $line, 1);
			$text = preg_replace ('/=[[:space:]]*$/', '', $text, 1);
			$text = trim ($text);
			array_push ($this->headings, array(1, $text));
			$num = $this->get_heading_num();
			return sprintf (
				"<h1 class=wiki><a name='#toc-%d'>%s %s</a></h1>",
				count($this->headings) - 1, $num, htmlspecialchars($text));
		}

		return false;
	}

	function format_list ($line)
	{
		# 1. something
		$pattern = '/^[[:space:]]+[[:digit:]]\.[[:space:]][^[:space:]]+/';
		if (preg_match($pattern, $line) > 0) {
			$nspaces = 0;
			while (ctype_space($line{$nspaces})) $nspaces++;

			$text = preg_replace ('/^[[:space:]]+[[:digit:]]\.[[:space:]]/', '', $line, 1);
			$text = '<li>' . htmlspecialchars($text);
			
			$count = count($this->list_stack);
			if ($count == 0 || $this->list_stack[$count-1][0] < $nspaces) {
				$text = "<ol type='1'>\n" . $text;
				$tmp = array ($nspaces, "ol");
				array_push ($this->list_stack, $tmp);
			}
			else if ($this->list_stack[$count-1] > $nspaces) {
				if ($count > 1) {
					$text = "</ol>\n" . $text;
					array_pop ($this->list_stack);
				}
			}
			return $text;
		}

		# a. something
		$pattern = '/^[[:space:]]+[a-z]\.[[:space:]][^[:space:]]+/';
		if (preg_match($pattern, $line) > 0) {
			$nspaces = 0;
			while (ctype_space($line{$nspaces})) $nspaces++;

			$text = preg_replace ('/^[[:space:]]+[a-z]\.[[:space:]]/', '', $line, 1);
			$text = '<li>' . htmlspecialchars($text);

			$count = count($this->list_stack);
			if ($count == 0 || $this->list_stack[$count-1][0] < $nspaces) {
				$text = "<ol type='a'>\n" . $text;
				$tmp = array ($nspaces, "ol");
				array_push ($this->list_stack, $tmp);
			}
			else if ($this->list_stack[$count-1] > $nspaces) {
				if ($count > 1) {
					$text = "</ol>\n" . $text;
					array_pop ($this->list_stack);
				}
			}

			return $text;
		}

		# A. something
		$pattern = '/^[[:space:]]+[A-Z]\.[[:space:]][^[:space:]]+/';
		if (preg_match($pattern, $line) > 0) {
			$nspaces = 0;
			while (ctype_space($line{$nspaces})) $nspaces++;

			$text = preg_replace ('/^[[:space:]]+[A-Z]\.[[:space:]]/', '', $line, 1);
			$text = '<li>' . htmlspecialchars($text);

			$count = count($this->list_stack);
			if ($count == 0 || $this->list_stack[$count-1][0] < $nspaces) {
				$text = "<ol type='A'>\n" . $text;
				$tmp = array ($nspaces, "ol");
				array_push ($this->list_stack, $tmp);
			}
			else if ($this->list_stack[$count-1] > $nspaces) {
				if ($count > 1) {
					$text = "</ol>\n" . $text;
					array_pop ($this->list_stack);
				}
			}

			return $text;
		}

		# * something
		$pattern = '/^[[:space:]]+\*[[:space:]][^[:space:]]+/';
		if (preg_match($pattern, $line) > 0) {
			$nspaces = 0;
			while (ctype_space($line{$nspaces})) $nspaces++;

			$text = preg_replace ('/^[[:space:]]+\*[[:space:]]/', '', $line, 1);
			$text = '<li>' . htmlspecialchars($text);

			$count = count($this->list_stack);
			if ($count == 0 || $this->list_stack[$count-1][0] < $nspaces) {
				$text = "<ul>\n" . $text;
				$tmp = array ($nspaces, "ul");
				array_push ($this->list_stack, $tmp);
			}
			else if ($this->list_stack[$count-1] > $nspaces) {
				if ($count > 1) {
					$text = "</ul>\n" . $text;
					array_pop ($this->list_stack);
				}
			}

			return $text;
		}

		return false;
	}

	function format_text ($line)
	{
		$text = htmlspecialchars($line);	

		/*
		// TODO: remember macro locations and replace them by actual values later...
		# [[[macro]]]
		$pattern = '/\[\[\[[:alnum:]+\]\]\]/';
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 2, strlen($link) - 4);

			$x = sprintf ("<a href='%s'>%s</a>", $link, $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}
		*/

		# [[[document name]]]
		$pattern = '/\[\[\[[[:alnum:][:space:]~!@#$%^&*()-+-={}|<>?,.;:]+\]\]\]/';
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 3, strlen($link) - 6);

			$x = sprintf ("<a href='%s'>%s</a>", $link, $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# [[document]]
		$pattern = '/\[\[[[:alnum:]~!@#$%^&*()_+-={}|<>?,.;:]+\]\]/';
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 2, strlen($link) - 4);

			list($cap, $url) = split (",", $link, 2);
			if ($url == "") {
				$x = sprintf ("<a href='%s%s'>%s</a>", 
					$link, $this->link_suffix, $link);
			}
			else {
				$x = sprintf ("<a href='%s%s'>%s</a>", 	
					$url, $this->link_suffix, $cap);
			}
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# '''''something'''''
		$pattern = "/'''''([^'][^[:space:],.]*[^']|[^'])'''''/";
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 5, strlen($link) - 10);

			$x = sprintf ("<b><i>%s</i></b>", $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# '''something'''
		$pattern = "/'''([^'][^[:space:],.]*[^']|[^'])'''/";
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 3, strlen($link) - 6);

			$x = sprintf ("<b>%s</b>", $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		# ''something''
		$pattern = "/''([^'][^[:space:],.]*[^']|[^'])''/";
		while (preg_match($pattern, $text, $matches) > 0) {
			$link = $matches[0];
			$link = substr ($link, 2, strlen($link) - 4);

			$x = sprintf ("<i>%s</i>", $link);
			$text = preg_replace ($pattern, $x, $text, 1);
		}

		return $text;
	}

}

?>

